#include  "chatroom.h"

char UserName[NAME_LEN];    //存储本机用户名
int UserNum;    //存储本机用户编号
int TotalUsers; //存储当前用户数量

pthread_t tid1; //线程号

void flushcache(void);
int UserNameCheck(void);
void ShowClientList(void);
int SelectTarget(int *CNumList);
void *MsgRecv(void *fd);
void MsgSend(int fd);

int main(int argc, char* argv[])
{
    int fd;
    char server_ip[20];
    if (argc ==1)
        strcpy(server_ip, "127.0.0.1"); //如果不指定server_ip，就默认为127.0.0.1，也就是localhost
    else if (argc == 2)
        strcpy(server_ip, argv[1]); //如果指定了IP地址，就按照指定的地址连接
    else
    {
        perror("Bad arguments!");
        exit(0);
    }
    Message Msg;

    //初始化socket端口的配置信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(server_ip);
    addr.sin_port = htons(PORT);

    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)   //尝试建立socket，将套接字保存在变量中
    {
        perror("Socket init failed!\n");
        exit(SOCKET_INIT_ERR);
    }
    if ((connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr))) == -1)  //尝试连接到服务器
    {
        perror("Socket connect failed!\n");
        exit(SOCKET_CONNECT_ERR);
    }

    read(fd, &Msg, sizeof(Msg));    //当服务器收到连接请求后，会判断是否允许连接，这里用于接收服务器的返回信息
    if (Msg.MsgType == LOGOUT)  //服务器拒绝了连接
    {
        printf("\e[33m[ERROR] Connection refused by the server!\e[0m\n");
        exit(CONNECTION_REFUSED);
    }
    else
    {
        memcpy(&CList, &Msg.CList, sizeof(ClientList)); //将服务器返回的客户端列表同步至本地
        UserNum = Msg.SourceNum;    //从服务器返回的信息中获取本机在服务器中对应的客户端号
        pthread_create(&tid1, NULL, MsgRecv, (void *)&fd);  //建立单独的线程，用于从服务器接受数据
        do
        {
            printf("Please enter you name: ");
            scanf("%s", UserName);
        } while(UserNameCheck());   //要求用户输入用户名，并与用户列表进行比对，如果用户名重复，就要求重新输入
        strcpy(Msg.SourceUser, UserName);;
        MsgSend(fd);    //将用户名装载到发送信息中，发送给服务器
        pthread_join(tid1, NULL);
    }
    return 0;
}

void flushcache(void)   //清除stdin缓冲区，防止意外
{
    char c;
    while (c!='\n'&&c!=EOF)
    {
        c = getc(stdin);
    }
}

int UserNameCheck(void) //检查用户名是否冲突
{
    int i;
    for (i = 0; i < MAX_CLIENTS; i++)
    {
        if (!strcmp(UserName,CList[i].name))    //将本地输入的用户名与所有在线的用户名进行对比
            break;  //如果有重复就跳出循环
    }
    if (i < MAX_CLIENTS)
    {
        printf("\e[33m[ERROR] User %s already exists!\e[0m\n", CList[i].name);  //循环没有完成，证明有冲突，返回1
        return 1;
    }
    else
        return 0;   //循环完成，没有冲突，返回0
}

void ShowClientList(void)   //显示在线用户列表
{
    int i;
    TotalUsers = 0;
    printf("\t _____________________________ \n");
    printf("\t|         CLIENT LIST         |\n");
    printf("\t|_____________________________|\n");
	printf("\t|\e[4m  Num    |    UserName       \e[24m|\n ");
    for (i = 0; i < MAX_CLIENTS; i++)
    {   //遍历用户列表
        if (CList[i].socketfd)  //socketfd不为0，证明用户存在
        {
            if (i == UserNum)   //用户是本机
            {
                printf("\t|\e[4;31m *%-4d   |  %-10s       \e[0m|\n", ++TotalUsers, CList[i].name); //输出信息标红
            }
            else    //用户不是本机
            {
                printf("\t|\e[4m   %-4d  |  %-10s       \e[24m|\n", ++TotalUsers, CList[i].name);   //普通输出
            }
        }
    }
    printf("\t|\e[4m        Total Clients:%-3d    \e[24m|\n", TotalUsers);  //显示当前在线人数
    return;
}

int SelectTarget(int *CNumList) //私聊时，选择目标用户
{
    int i, n;
    n = 0;
    ShowClientList();   //显示在线用户列表
    for (i = 0; i < MAX_CLIENTS; i++)
    {
        if (CList[i].socketfd)
        {
            CNumList[n] = i;
            n++;
        }
    }   //建立一个数组，存储各个用户在用户列表中的位置
    int selection;
    printf("Please enter the number of your target user:");
    if ((scanf("%d", &selection)) != 1) //用户输入选择结果
    {
       flushcache();
       printf("\e[33m[ERROR] Bad input!\e[0m\n");
       return -1;
    }
    if (selection <= TotalUsers)    //选择内容正确
    {
        if (CNumList[selection - 1] == UserNum) //用户错误地选择了自己
        {
            printf("\e[33m[ERROR] You really don't have to send message to yourself.\e[0m\n");           
            return -1;     
        }
        else
        {
            return CNumList[selection - 1]; //返回选择的用户
        }
    }
    else    //选择了不存在的内容
    {
        printf("\e[33m[ERROR] Bad selection!\e[0m\n");
        return -1;
    }
}

void *MsgRecv(void *fd) //接收信息
{
    int sockfd = *(int *)fd;
    Message Msg;
    while (1)
    {
        bzero(&Msg, sizeof(Msg));
        Msg.MsgType = ERROR;    //初始化变量内容
        read(sockfd, &Msg, sizeof(Msg));    //从服务器接受数据
        if (Msg.MsgType == ERROR)
            break;  //服务器发生错误，跳出循环
        
        switch (Msg.MsgType)    //根据信息类型决定操作
        {
            case LOGIN: //有登陆信息
            {
                if (Msg.SourceNum == UserNum)   //自己的登陆信息
                    printf("\e[34m[Login] You have successfully logged in to the server.\e[0m\n");
                else    //别人的登陆信息
                    printf("\e[34m[Login] New login from: %-10s.\e[0m\n", Msg.SourceUser);
                break;
            }
            case LOGOUT:    //有登出信息
            {
                printf("\e[34m[Logout] User: %-10s have logged out.\e[0m\n", Msg.SourceUser);
                break;
            }
            case PUBLIC:    //有广播信息
            {
                printf("\e[32m[Public] From: %-10s, Msg: %s\e[0m\n", Msg.SourceUser, Msg.MsgContent);
                break;
            }
            case PRIVATE:   //有发送给本机的私聊信息
            {
                printf("\e[31m[Private] From: %-10s, Msg: %s\e[0m\n", Msg.SourceUser, Msg.MsgContent);
                break;
            }
            default:
                break;
        }
        memcpy(&CList, &Msg.CList, sizeof(CList));  //顺便刷新一下在线列表
    }
    printf("\e[33m[Info] Connection closed, terminating the process.\e[0m\n");  //关闭程序
    exit(1);
}

void MsgSend(int fd)    //发送信息
{
    Message Msg;
    Msg.MsgType = LOGIN;
    Msg.SourceNum = UserNum;
    strcpy(Msg.SourceUser, UserName);
    strcpy(Msg.MsgContent, UserName);   //用本机的参数初始化发送信息
    write(fd, &Msg, sizeof(Msg));   //发送信息至服务器

    int CNumList[MAX_CLIENTS];  //使用一个数组记录在用户列表中各个用户的位置
    int sel;

    while (1)
    {
        printf("Please select:\n");
        printf("1-Public  2-Private  3-Client list  4-EXIT\n");
        if (scanf("%d", &sel) != 1)
        {
            sel = 0;
            flushcache();
        }   //让用户选择功能
        bzero(&Msg, sizeof(Msg));
        Msg.SourceNum = UserNum;
        strcpy(Msg.SourceUser, UserName);   //用本机的参数初始化发送信息
        switch (sel)    //根据用户选择决定执行的功能
        {
            case 1: //用户选择发送广播
            {
                Msg.MsgType = PUBLIC;   //设定信息类型为广播信息
                printf("\e[32m[Public] Please enter your message: \e[0m");
                flushcache();
                fgets(Msg.MsgContent, sizeof(Msg.MsgContent), stdin);
                Msg.MsgContent[strlen(Msg.MsgContent) - 1] = '\0';  //读取输入信息
                write(fd, &Msg, sizeof(Msg));   //发送给服务器
                break;
            }
            case 2: //用户选择私聊
            {
                Msg.MsgType = PRIVATE;  //设定信息类型为私聊消息
                bzero(CNumList, sizeof(CNumList));  //初始化用户清单数组
                if ((Msg.TargetNum = SelectTarget(CNumList)) != -1) //让用户选择发送目标
                {
                    printf("\e[31m[Private] Please enter your message to %-10s: \e[0m", CList[Msg.TargetNum].name);
                    flushcache();
                    fgets(Msg.MsgContent, sizeof(Msg.MsgContent), stdin);
                    Msg.MsgContent[strlen(Msg.MsgContent) - 1] = '\0';  //读取输入信息
                    write(fd, &Msg, sizeof(Msg));   //发送给服务器
                }
                break;
            }
            case 3: //用户选择查看在线用户列表
            {
                ShowClientList();   //那就给他康康
                break;
            }
            case 4: //用户选择退出
            {
                printf("\e[33m[Exit] Logging out.\e[0m\n");
                Msg.MsgType = LOGOUT;   //设定信息类型为登出信息
                strcpy(Msg.MsgContent, "Goodbye!");
                write(fd, &Msg, sizeof(Msg));   //发送给服务器
                break;
            }
            default:
            {
                printf("\e[33m[ERROR] Bad input!\e[0m\n");
                Msg.MsgType = 0;    //用户输入了错误的信息，则提示输入错误
                break;
            }
        }
        if (Msg.MsgType == LOGOUT)
            break;  //如果用户选择退出，则跳出循环，程序结束
    }
    pthread_cancel(tid1);   //结束线程
}